package hamaster.jpbc.crypt.impl;

import hamaster.jpbc.Main;
import hamaster.jpbc.crypt.Crypt;
import hamaster.jpbc.crypt.CryptIO;
import hamaster.jpbc.crypt.aes.AES;
import hamaster.jpbc.exception.CryptException;
import hamaster.jpbc.gui.ProgressListener;
import hamaster.jpbc.userconfig.ConfigKeys;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * CryptIO接口使用单一密钥的简单AES实现
 * @author <a href="mailto:wangyeee@gmail.com">Wang Ye</a>
 */
public class SimpleCryptIOImpl implements CryptIO {

	private int blockSize;

	protected long length;

	protected boolean autoClose;

	private Crypt crypt;

	protected ProgressListener progressListener;

	protected SimpleCryptIOImpl() {
		try {
			blockSize = Integer.parseInt(Main.getUserConfig().getProperty(ConfigKeys.IO_BLOCK_SIZE));
		} catch (NumberFormatException e) {
			blockSize = 1024;
		}
	}

	/**
	 * 创建一个使用默认AES/ECB加密的SimpleCryptIOImpl实例
	 * @param key AES密钥
	 * @param length 进行加解密操作文件的长度
	 */
	public SimpleCryptIOImpl(byte[] key, long length) {
		this(key, length, false);
	}

	/**
	 * 创建一个新的SimpleCryptIOImpl实例 需要在创建后调用<code>setCrypt</code>方法指定使用的加密算法
	 * @param length 进行加解密操作文件的长度
	 * @param autoClose 是否自动关闭输出流
	 */
	public SimpleCryptIOImpl(long length, boolean autoClose) {
		this();
		this.length = length;
		this.autoClose = autoClose;
		progressListener = new ProgressListener() {
			/** 如果不设置监听器 什么也不做 */
			@Override
			public void progressUpdated(int progress) {
			}
		};
	}

	/**
	 * 创建一个默认AES/ECB加密的SimpleCryptIOImpl实例
	 * @param key AES密钥
	 * @param length 进行加解密操作文件的长度
	 * @param autoClose 是否自动关闭输出流
	 */
	public SimpleCryptIOImpl(byte[] key, long length, boolean autoClose) {
		this(length, autoClose);
		crypt = new AES();
		crypt.setBlockSize(blockSize);
		crypt.setKey(key);
	}

	@Override
	public void encrypt(File source, OutputStream destination) throws IOException, CryptException {
		InputStream in = new FileInputStream(source);
		byte[] buffer = new byte[blockSize];
		length = in.available();
		long byteswrite = 0L;
		int read = 0;
		progressListener.progressUpdated(0);
		while ((read = in.read(buffer)) > 0) {
			byteswrite += read;
			byte[] cipher = crypt.encrypt(buffer);
			destination.write(cipher);
			progressListener.progressUpdated((int) (byteswrite * 100 / length));
		}
		in.close();
		destination.flush();
		if (autoClose)
			destination.close();
		progressListener.progressUpdated(100);
	}

	@Override
	public void decrypt(File source, OutputStream destination) throws IOException, CryptException {
		InputStream in = new FileInputStream(source);
		byte[] buffer = new byte[blockSize];
		long bytesread = 0L;
		int read = 0;
		progressListener.progressUpdated(0);
		while ((read = in.read(buffer)) > 0) {
			bytesread += read;
			byte[] cipher = crypt.decrypt(buffer);
			long offset = blockSize;
			if (bytesread - length > 0)
				offset = blockSize - bytesread + length;
			destination.write(cipher, 0, (int) offset);
			progressListener.progressUpdated((int) ((bytesread - blockSize) * 100 / length));
		}
		in.close();
		destination.flush();
		if (autoClose)
			destination.close();
		progressListener.progressUpdated(100);
	}

	@Override
	public void setProgressListener(ProgressListener progressListener) {
		this.progressListener = progressListener;
	}

	@Override
	public void setCrypt(Crypt crypt) {
		this.crypt = crypt;
	}

	@Override
	public void setBlockSize(int blockSize) {
		this.blockSize = blockSize;
	}
}
